/*  This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or (at
    your option) any later version.
    For more details, see the GNU Lesser General Public License (www.fsf.org
    or the COPYING file somewhere in the package)
 */

#include "Platform.hh"

/*! @file src/OSGi/Platform.cc
    @brief Class OSGi::Platform (non-inline) methods.
    @author @ref Guillaume_Terrissol
    @date 17th December 2009 - 22nd January 2014
    @note This file is distributed under the LGPL license.
    Refer to the file COPYING (or http://www.fsf.org) for more information.
 */

#if defined(_WIN32)
#   include <iomanip>
#   include <map>
#   include <sstream>
#   include <windows.h>
#else
#   include <sys/utsname.h>
#endif  // defined(_WIN32)

#include <string>

#include "Exception.hh"

namespace OSGi
{
//------------------------------------------------------------------------------
//                            Windows Implementation
//------------------------------------------------------------------------------

#if   defined(_WIN32)

    namespace
    {
        /*! Generates an id for a Windows OS given a few pieces of information.
            @param pPlatform Identifies the Windows family (9x/NT)
            @param pMajor    Identifies the major version
            @param pMinor    Identifies the minor version
            @return A unique Id for the given arguments
         */
        inline long osId(long pPlatform, long pMajor, long pMinor)
        {
            return (pPlatform << 16) + (pMajor << 8) + pMinor;
        }
    }


    /* Windows specific.
     */
    std::string Platform::osName()
    {
        return "Windows";
    }


    /* Windows specific.
     */
    std::string Platform::osLongerName()
    {
        OSVERSIONINFO vi;
        vi.dwOSVersionInfoSize = sizeof(vi);
        if (GetVersionEx(&vi) == 0)
        {
            throw Exception{"Cannot get OS version information"};
        }

        static std::map<int, std::string>   sOsNames =
        {
            { osId(VER_PLATFORM_WIN32_WINDOWS, 4,  0), "Windows 95" },
            { osId(VER_PLATFORM_WIN32_WINDOWS, 4, 10), "Windows 98" },
            { osId(VER_PLATFORM_WIN32_WINDOWS, 4, 90), "Windows Me" },
            { osId(VER_PLATFORM_WIN32_NT,      4,  0), "Windows NT" },
            { osId(VER_PLATFORM_WIN32_NT,      5,  0), "Windows 2000" },
            { osId(VER_PLATFORM_WIN32_NT,      5,  1), "Windows XP" },
            { osId(VER_PLATFORM_WIN32_NT,      5,  2), "Windows Server 2003" },
            { osId(VER_PLATFORM_WIN32_NT,      6,  0), "Windows Vista" },
            { osId(VER_PLATFORM_WIN32_NT,      6,  1), "Windows Seven" }
        };

        switch (vi.dwPlatformId)
        {
            case VER_PLATFORM_WIN32s:
                return "Windows 3.x";
            case VER_PLATFORM_WIN32_WINDOWS:
            case VER_PLATFORM_WIN32_NT:
            default:
                {
                    long    lOsId = osId(vi.dwPlatformId,
                                         vi.dwMajorVersion,
                                         vi.dwMinorVersion);
                    auto    lIter = sOsNames.find(lOsId);
                    if (lIter != end(sOsNames))
                    {
                        return lIter->second;
                    }
                }
                break;
        }

        return "Unknown";
    }


    /* Windows specific.
     */
    std::string Platform::osVersion()
    {
        OSVERSIONINFO vi;
        vi.dwOSVersionInfoSize = sizeof(vi);
        if (GetVersionEx(&vi) == 0)
        {
            throw Exception{"Cannot get OS version information"};
        }

        return std::to_string(vi.dwMajorVersion)
             + '.'
             + std::to_string(vi.dwMinorVersion)
             + " (Build "
             + std::to_string(vi.dwBuildNumber & 0xFFFF)
             + ((vi.szCSDVersion[0] != 0) ? " : " + std::string{vi.szCSDVersion} : "")
             + ')';
    }


    /* Windows specific.
     */
    std::string Platform::osArchitecture()
    {
        SYSTEM_INFO si;
        GetSystemInfo(&si);
        switch (si.wProcessorArchitecture)
        {
            case PROCESSOR_ARCHITECTURE_INTEL:
                return "IA32";
            case PROCESSOR_ARCHITECTURE_MIPS:
                return "MIPS";
            case PROCESSOR_ARCHITECTURE_ALPHA:
                return "ALPHA";
            case PROCESSOR_ARCHITECTURE_PPC:
                return "PPC";
            case PROCESSOR_ARCHITECTURE_IA64:
                return "IA64";
#   ifdef PROCESSOR_ARCHITECTURE_IA32_ON_WIN64
            case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64:
                return "IA64/32";
#   endif
#   ifdef PROCESSOR_ARCHITECTURE_AMD64
            case PROCESSOR_ARCHITECTURE_AMD64:
                return "AMD64";
#   endif
            default:
                return "Unknown";
        }
    }


    /* Windows specific.
     */
    std::string Platform::hostName()
    {
        char name[MAX_COMPUTERNAME_LENGTH + 1];
        DWORD size = sizeof(name);
        if (GetComputerNameA(name, &size) == 0)
        {
            throw Exception{"Cannot get computer name"};
        }
        return name;
    }

#else   // defined(_WIN32)

//------------------------------------------------------------------------------
//                              *n*x Implementation
//------------------------------------------------------------------------------

    /* *n*x generic.
     */
    std::string Platform::osName()
    {
        struct utsname uts;
        uname(&uts);
        return uts.sysname;
    }


    /* *n*x generic.
     */
    std::string Platform::osLongerName()
    {
        struct utsname uts;
        uname(&uts);
        return osName() + " " + osVersion();
    }


    /* *n*x generic.
     */
    std::string Platform::osVersion()
    {
        struct utsname uts;
        uname(&uts);
        return uts.release;
    }


    /* *n*x generic.
     */
    std::string Platform::osArchitecture()
    {
        struct utsname uts;
        uname(&uts);
        return uts.machine;
    }


    /* *n*x generic.
     */
    std::string Platform::hostName()
    {
        struct utsname uts;
        uname(&uts);
        return uts.nodename;
    }
#endif  // defined(_WIN32)

//------------------------------------------------------------------------------
//                             Methods Documentation
//------------------------------------------------------------------------------

    /*! @fn std::string Platform::osName()
        @retval For windows, the complete name
        @retval For *n*x, the `uname -s` output
     */


    /*! @fn std::string Platform::osVersion()
        @retval For windows, the version (with build number)
        @retval For *n*x, the `uname -v` output
     */


    /*! @fn std::string Platform::osArchitecture()
        @retval For windows, the generic CPU name (e.g. IA32, MIPS, ALPHA, etc)
        @retval For *n*x, the `uname -m` output
     */


    /*! @fn std::string Platform::hostName()
        @retval For windows, the host name
        @retval For *n*x, the `uname -n` output
     */
}
